package ar.uba.fi.tonyvaliente.tree;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import ar.uba.fi.tonyvaliente.files.File;
import ar.uba.fi.tonyvaliente.files.FileException;
import ar.uba.fi.tonyvaliente.files.Persistor;
import ar.uba.fi.tonyvaliente.files.RecordPosition;

/**
 * Implementación en disco del árbol B
 * @author santiago
 */
public class DiskBTree<K extends Comparable<K>, V> extends BTree<K, V> {

	private static Log log = LogFactory.getLog(DiskBTree.class);
	
	private File file;
	private BTreeFileWrapper<K,V> wrapper;
	protected RecordPosition rootNodePosition;

	/**
	 * Crea un nuevo arbol B en disco con un solo nodo (raiz) vacio
	 * y la cantidad de elementos por nodo dada por parametro
	 * @param elementosPorNodo cantidad maxima de elementos por cada nodo 
	 * @param pathName ruta del archivo donde se guardara el arbol
	 * @param keyPersistor persistor para las claves
	 * @param valuePersistor persistor para los valores
	 * @throws BTreeException si ocurre un error al crear el arbol
	 */
	public DiskBTree(Integer elementosPorNodo, String pathName,
			Persistor<K> keyPersistor, Persistor<V> valuePersistor) throws BTreeException {
		
		super(elementosPorNodo);
		try {
			this.file = File.create(pathName, 8192);
			this.wrapper = new BTreeFileWrapper<K,V>(file, keyPersistor, valuePersistor);
		} catch (FileException e) {
			log.fatal("Error creando el BTree", e);
			throw new BTreeException("Error creando el BTree", e);
		}

		try{
			BReference<K, V> reference = this.createReference();
			reference.setNode(new BTreeNode<K, V>(this/*, elementosPorNodo*/));
			rootNode = reference.getNode();
			rootNode.setThisReference(reference);
			rootNode.setTree(this);
			rootNodePosition = ((DiskBReference<K, V>)reference).getRecordPosition();
			this.elementosPorNodo = elementosPorNodo;
			wrapper.setRootNodePosition(rootNodePosition);
			wrapper.setMaxCantidadElementosNodo(elementosPorNodo);
		}catch(BTreeException e){
			log.fatal("Se produjo un error creando la raiz del árbol", e);
			throw e;
		}
	}

	public DiskBTree(String pathName,boolean readOnly,
			Persistor<K> keyPersistor, Persistor<V> valuePersistor) throws BTreeException {
		
		super();
		try {
			this.file = File.open(pathName, readOnly);
			this.wrapper = new BTreeFileWrapper<K,V>(file, keyPersistor, valuePersistor);
			this.elementosPorNodo = wrapper.getMaxCantidadElementosNodo();
		} catch (FileException e) {
			log.fatal("Error abriendo el BTree" , e);
			throw new BTreeException("Error abriendo el BTree, path: " + pathName, e);
		}

		try{
			this.loadRootNode();
		}catch(BTreeException e){
			log.fatal("Se produjo un error obteniendo la raiz del árbol", e);
			throw e;
		}
	}


	/* (non-Javadoc)
	 * @see ar.uba.fi.datos.tonyvaliente.tree.BTree#createReference()
	 */
	@Override
	protected BReference<K, V> createReference() {

		BReference<K, V> reference = new DiskBReference<K, V>(this.wrapper);
		return reference;
	}

	@Override
	public void close() throws BTreeException{
		this.wrapper.close();
	}
	
	private void loadRootNode() throws BTreeException {
		// Obtengo el nodo raiz desde el archivo
		RecordPosition rootPos = wrapper.getRootNodePosition();
		int block = rootPos.getBlockNumber();
		int offset = rootPos.getOffset();
		rootNode = wrapper.getNode(block, offset);
		rootNode.setTree(this);
		rootNodePosition = rootPos;
		
		// Seteo en el nodo la referencia a si mismo
		DiskBReference<K, V> reference;
		reference = (DiskBReference<K, V>)this.createReference();
		reference.setNode(rootNode, false); // para que no escriba el nodo en disco
		reference.setRecordPosition(rootNodePosition);
		rootNode.setThisReference(reference);
	}

	protected void setRootNode(BReference<K, V> rootRef) throws BTreeException {
		DiskBReference<K, V> diskRef = (DiskBReference<K, V>)rootRef;
		rootNode = diskRef.getNode();
		rootNode.setTree(this);
		rootNodePosition = diskRef.getRecordPosition();
		wrapper.setRootNodePosition(rootNodePosition);
	}
}
